import subprocess       # For starting external jobs
import os               # For interacting with the filesystem & pipes
import time             # For start/end times
import random           # For testing
import threading        # For threads (duh!)
import tkFileDialog     # For selecting files
import xml.dom.minidom  # For xml (duh!)

### examples:
##def open_it():
##    filename = tkFileDialog.askopenfilename()
##    print filename  # test
##    
##def save_it():
##    filename = tkFileDialog.askopenfilename()
##    print filename  # test
##    
##def save_as():
##    filename = tkFileDialog.asksaveasfilename()
##    print filename  # test

class PipePairEnds:
    def __init__(self,Send,Receive):
        self.Send    = Send;
        self.Receive = Receive;

class PipePair:
    def __init__(self):
        LeftReceive,RightSend = os.pipe();
        RightReceive,LeftSend = os.pipe();
        self.Left  = PipePairEnds(LeftSend, LeftReceive);
        self.Right = PipePairEnds(RightSend, RightReceive);


class MenuThread(threading.Thread):
    # Override __init__() method to allow us to do something with the arguments
    def __init__(self,pipepairends,name=None):
        if not name:
            threading.Thread.__init__(self, None, None);
        else:
            threading.Thread.__init__(self, None, None, name);
        self._menu = dict();
        self._keylist = list();
        self._pipes=pipepairends;
    # Override the run method
    def run(self):
        exitFlag=False;
        while not exitFlag:
            done=False;
            while not done:
                print "please select an option from the menu:";
                for key in self._keylist:
                    print "["+key+"] - "+self._menu[key][0];
                char=raw_input("> ");
                if len(char)==1:
                    # case-insensitive comparison
                    char = char.lower();
                    if char in self._keylist:
                        done=True;
                    else:
                        print "Error: unrecognised option", char,\
                              "passed. Please try again.";
                else:
                    print "Error: option codes are single letters!";

#            print "You picked option ["+char+"], "+self._menu[char][0]+".";
            self._menu[char][1](char);
            if char=="q":
                exitFlag=True;
    def _addMenuChoice(self,key,values):
        if not type(key)==type(""):
            raise TypeError(str(type(key))+" key passed. "+str(type(self))+" requires "+str(type(""))+" keys");
        if self._menu.has_key(key):
            raise RuntimeError(str(self)+" already has a choice for ["+key+"]");
        
        self._menu[key]=values;
        self._keylist.append(key);
    def _sendMenuCode(self,key,data=None):
        print "sending message with key "+key;
        os.write(self._pipes.Send,key+"::"+data);
        print os.read(self._pipes.Receive,1024);


class ListMenuThread (MenuThread):
    _fileoptions = {'filetypes':[('xml files', '.xml'), ('text files', '.txt')],
           #'initialdir': 'C:\\',
           #'initialfile': 'myfile.txt',
           'title': 'Select a list file to use'};
    namestem="ListMenu";
    #override init to add menu functions
    def __init__(self,pipepairends):
        MenuThread.__init__(self,pipepairends,self.namestem+str(random.randint(1,99)));
        keys         = ('n','l','a', 'd', 'c', 'x', 's', 'r', 'q');
        values = (
          ("create a new list file to use",self.newlistfilefn),
          ("pick list file to use",self.listselectfn),
          ("add a job to the run list",self.addjobfn),
          ("delete a job from the run list",self.removejobfn),
          ("clear the run list",self.clearlistfn),
          ("stop runner immediately",self.emergencystopfn),
          ("stop runner after current job",self.delayedstopfn),
          ("start running job list",self.startlistfn),
          ("quit command-line",self.quitfn));
        assert len(keys)==len(values) , "There must be an equal number of keys "+\
                                        "& menu items!";
        for i in range(len(keys)):
            self._addMenuChoice(keys[i],values[i]);

    # Menu functions
    def quitfn(self,char):
        self._sendMenuCode(char,"");
        print self.name+" exiting";
    def addjobfn(self,char):
        self._sendMenuCode(char,"");
#        print("item added.");
    def removejobfn(self,char):
        self._sendMenuCode(char,"");
#        print("item removed.");
    def clearlistfn(self,char):
        self._sendMenuCode(char,"");
#        print("job list cleared.");
    def emergencystopfn(self,char):
        self._sendMenuCode(char,"");
#        print("emergencystopfn");
        self._sendMenuCode(char,"");
    def delayedstopfn(self,char):
        self._sendMenuCode(char,"");
#        print "delayed stop acknowledged";
    def startlistfn(self,char):
        self._sendMenuCode(char,"");
#        print("starting listrunner");
    def listselectfn(self,char):
        filename = tkFileDialog.askopenfilename(**self._fileoptions);
        self._sendMenuCode(char,filename);
#        print filename;
    def newlistfilefn(self,char):
        filename = tkFileDialog.asksaveasfilename(**{'title':'Please select a location to save the new list file','filetypes':[('xml file','.xml')]});
        self._sendMenuCode(char,filename);
#        print filename;


# My ListRunner class
class ListRunnerThread (threading.Thread):
    """Executes an external program or batch file for each of a list of files
    contained in an xml specification"""
    time_format_reading="%d/%m/%Y %X";
    time_format_filename="%Y%d%m_%X";
    namestem="ListRunner";
    defaultjob="timewaster.bat";
    def __init__(self,pipepairends,jobtorun="",listpath=""):
        threading.Thread.__init__(self,None,None,self.namestem+str(random.randint(0,99)));
        self._path = listpath;
        self._xmldoc = xml.dom.minidom.Document();
        self._statinfo = os.stat(self._path);
        self._goflag=False;
        self._procflag=False;
        self._pipes=pipepairends;
        if jobtorun=="":
            jobtorun=self._defaultjob;
        self._jobtorun=jobtorun;
        
        self.readxml();
        return
    def run(self):
        # serve 10 requests
        exitFlag=False
        counter = 1;
        while not exitFlag:
            data = os.read(self._pipes.Receive,1024);
            data=data.partition("::");
            response = self.name+" req "+str(counter)+" - "+str(data)+"\nresponse:"+str(bool(random.randint(0,1)));
            os.write(self._pipes.Send,response);
            if data[0]=="q":
                print self.name+" exiting.";
                exitFlag=True;
            counter += 1;
##        print "processing list "+self._path
##        while self.jobswaiting()>0:
##            success=self.processjob();
##            #raw_input("this is a pause, ok?");
##            self._currentjobfinished(success);

    def currentjob(self):
        return self._currentjob.cloneNode(True);
    def readxml(self):
        newinfo = os.stat(self._path);
#        print "re-checking xml";
        self._xmldoc = xml.dom.minidom.parse(self._path);
        self._stripjunk(self._xmldoc);
        # get blocks together
        self._aggregate("torun");
        self._aggregate("succeeded");
        self._aggregate("failed");
        # adjust current running item
        self._writecurrentjob();
        return
    def _stripjunk(self,node):
        dellist=list();
        for child in node.childNodes:
            if child.nodeType in (xml.dom.Node.TEXT_NODE,):
                dellist.append(child);
            else:
                self._stripjunk(child);
        for item in dellist:
            item.parentNode.removeChild(item);
        return None
    def writexml(self):
#        print "writing new xml"
        _fileobj=open(self._path,"w");
        _fileobj.write(self._xmldoc.toprettyxml());
        _fileobj.close();
        return    
    def _writecurrentjob(self):
        self._aggregate("current",True);
        # select the "current" tag in the document, creating it if it doesn't
        # yet exist
        try:
            curr_el = self._xmldoc.getElementsByTagName("current")[0];
        except IndexError:
            curr_el = self._xmldoc.createElement("current");
            self._xmldoc.childNodes[0].appendChild(curr_el);
        # add the current job into the <current> tag
        try:
            curr_el.appendChild(self._currentjob);
        except AttributeError:
            pass
        return
    def _currentjobfinished(self,success):
        # Read xml first, to check for any new jobs that were added while we
        # were running the one we've just finished
        self.readxml();
        # Write data to the finished job
        self._currentjob.setAttribute("endtime",time.strftime(ListReader.time_format_reading));
        # Move finished job from the current to the appropriate
        # completed list
        self._currentjob.parentNode.removeChild(self._currentjob);
        if success:
            listname="succeeded";
        else:
            listname="failed";
        self._xmldoc.getElementsByTagName(listname)[0].appendChild(self._currentjob);
        # Write modified data to xml.
        self.writexml();
        return

    def _picknextjob(self):
        newjob = self._xmldoc.getElementsByTagName("torun")[0].getElementsByTagName("FDTDjob")[0];
        newjob.parentNode.removeChild(newjob);
        self._currentjob = newjob;
        self._xmldoc.getElementsByTagName("current")[0].appendChild(self._currentjob);
        return self._currentjob.cloneNode(True);

    def _aggregate(self,elementname,empty=False):
        allelements = self._xmldoc.getElementsByTagName(elementname);
        if allelements.length>0:
            if empty:
                start=0;
            else:
                start=1;
            for element in allelements[start:allelements.length]:
                while element.childNodes.length:
                    if empty:                        
                        element.childNodes[0].parentNode.removeChild(element.childNodes[0]);
                    else:
                        # add child to the first "elementname" element
                        allelements[0].appendChild(element.childNodes[0]);
                # remove the element from the document tree
                element.parentNode.removeChild(element);
        else:
            # create an element of the appropriate name.
            allelements=[self._xmldoc.createElement(elementname)];
            self._xmldoc.childNodes[0].appendChild(allelements[0]);
        return allelements[0];

    def jobswaiting(self):
        return self._xmldoc.getElementsByTagName("torun")[0].getElementsByTagName("FDTDjob").length

    def processjob(self):        
        self._picknextjob();
        self._currentjob.setAttribute("starttime",time.strftime(self.time_format_reading));
        self.writexml();
        
##        newproc = subprocess.Popen(os.path.abspath(self._jobtorun+" "+self._currentjob.getAttribute("file")),0,None,None,subprocess.PIPE);
        newproc = subprocess.Popen(self._jobtorun+"10",0,None,None,subprocess.PIPE);
        counter = 0;
        while not self._procflag:
            time.sleep(1);
            print newproc.poll()
            if counter==9:
                self._procflag=True;
            
        newproc.wait();
        print newproc.communicate()[0].replace("\r","");
        return bool(random.randint(0,1));

pipes= PipePair();
lt = ListRunnerThread(pipes.Left,jobtorun="H:\josh-phd-code\batch\timewaster.bat",listpath="fdtd-jobs-list.xml")
lt.start();
mt=ListMenuThread(pipes.Right);
mt.start();
#import createsamplelist
#createsamplelist.refresh_list_file(1,10);
#lt = ListRunnerThread("LRT1","H:\josh-phd-code\batch\timewaster.bat","fdtd-jobs-list.xml");
